#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "znet.h"
#include "tunnel.h"

uint8_t buffer[1024] = {0};

list_t pending_list;

typedef struct {
    uint8_t cmd;
    uint32_t channel;
    zsess_t *s;
    zsess_t *matched; // TX match RX
    list_t node;
} pad_t;


int pad_new(zsess_t *s)
{
    s->user.ptr = NULL;

    pad_t *pad = malloc(sizeof(pad_t));
    if (pad == NULL)
        return -1;

    memset(pad, 0, sizeof(pad_t));
    pad->s = s;
    s->user.ptr = pad;

    return 0;
}

void pad_del(zsess_t *s)
{
    pad_t *pad = (pad_t *)s->user.ptr;
    if (pad != NULL) {
        list_t *i;
        list_for_each(i, &pending_list) {
            if (list_entry(i, pad_t, node) == pad) {
                list_remove(&pad->node);
                break;
            }
        }
        free(pad);
    }
    s->user.ptr = NULL;
}

/*
 * RETURN
 *  0: matched or added to pending list
 * -1: failed, should close socket, socket disconnected or parse tunnel proto failed
 * */
int pad_match(zsess_t *s)
{
    pad_t *pad = (pad_t *)s->user.ptr;
    if (pad->cmd == TUNNEL_CMD_NONE) { // pad is not inited.
        /* get req */
        printf("getreq\n");
        if (tunnel_get_req(s->sock, &pad->cmd, &pad->channel) < 0)
            return -1;
        printf("-\n");

        /* match another pad */
        pad_t *tmp;
        uint8_t pad_cmd = (pad->cmd == TUNNEL_CMD_TX) ? TUNNEL_CMD_RX : TUNNEL_CMD_TX;
        list_t *i;
        list_for_each(i, &pending_list) {
            tmp = list_entry(i, pad_t, node);
            if (pad->channel == tmp->channel && pad_cmd == tmp->cmd) {
                printf("matched\n");
                /* matched */
                list_remove(&tmp->node);
                pad->matched = tmp->s;
                tmp->matched = pad->s;
                // int tx_sock = (pad->cmd == TUNNEL_CMD_TX) ? pad->s->sock : tmp->s->sock;
                tunnel_go(pad->s->sock);
                tunnel_go(tmp->s->sock);
                return 0;
            }
        }
        /* not matched */
        list_insert(&pending_list, &pad->node);
        printf("pending\n");
    }
    return 0;
}

void printbuf(uint8_t *buf, int len)
{
    for (int i = 0; i < len; i++)
        printf("%02x ", buf[i]);
    printf("\n");
}

/************************************************************************************/

void on_sess_recv(zsess_t *s)
{
    pad_t *pad = (pad_t *)s->user.ptr;
    int ret;

    if (pad == NULL)
        goto exit;

    printf("list len: %d\n", list_len(&pending_list));

    if (pad->matched == NULL) {
        if (pad_match(s) < 0) {
            goto exit;
        }
        return;
    }

    int matched_sock = pad->matched->sock;

    ret = znet_recv(s->sock, buffer, sizeof(buffer));
    if (ret <= 0)
        goto exit;

    ret = znet_send(matched_sock, buffer, ret);
    if (ret <= 0)
        goto exit;

    return;

exit:
    znet_shutdown(s->sock);
    znet_shutdown(matched_sock);
}

int on_sess_open(zsess_t *s)
{
    printf("[%s:%d] +\n", s->addr, s->port);

    if (pad_new(s) < 0)
        znet_shutdown(s->sock);

    return 0;
}

void on_sess_exit(zsess_t *s)
{
    printf("[%s:%d] -\n", s->addr, s->port);

    pad_del(s);
}

void on_server_launch(int port)
{
    printf("server port: %d\n", port);
}

int main(int argc, char *argv[])
{
    if (argc != 2) {
        printf("tunnel <port>\n");
        return -1;
    }

    if (argc != 2) {
        printf("tunnel <port>\n");
        return 0;
    }

    list_init(&pending_list);

    int port = atoi(argv[1]);

    znet_t srv = {
        .port = port,
        .on_launch = on_server_launch,
        .on_open = on_sess_open,
        .on_recv = on_sess_recv,
        .on_exit = on_sess_exit,
    };

    return znet_launch(&srv, ZNET_SERVER);
}
